import * as PIXI from "https://cdn.skypack.dev/pixi.js";
import { KawaseBlurFilter } from "https://cdn.skypack.dev/@pixi/filter-kawase-blur";
import SimplexNoise from "https://cdn.skypack.dev/simplex-noise";
import hsl from "https://cdn.skypack.dev/hsl-to-hex";
import debounce from "https://cdn.skypack.dev/debounce";

// 返回某个范围内的随机数
function random(min, max) {
  return Math.random() * (max - min) + min;
}

// 将一个数字从 1 个范围映射到另一个范围
function map(n, start1, end1, start2, end2) {
  return ((n - start1) / (end1 - start1)) * (end2 - start2) + start2;
}

// 创建新的单面噪声实例
const simplex = new SimplexNoise();

// 色阶
class ColorPalette {
  constructor() {
    this.setColors();
    this.setCustomProperties();
  }

  setColors() {
    // 随机选择介于 220 和 360 之间的色调
    this.hue = ~~random(220, 360);
    this.complimentaryHue1 = this.hue + 30;
    this.complimentaryHue2 = this.hue + 60;
    // 定义固定的饱和度和亮度
    this.saturation = 95;
    this.lightness = 50;

    // 定义基色
    this.baseColor = hsl(this.hue, this.saturation, this.lightness);
    // 定义一个随机的颜色，30度远离边框
    this.complimentaryColor1 = hsl(
      this.complimentaryHue1,
      this.saturation,
      this.lightness
    );
    // 定义第二种互补色，距底座 60 度
    this.complimentaryColor2 = hsl(
      this.complimentaryHue2,
      this.saturation,
      this.lightness
    );

    // 将颜色选择存储在数组中，以便以后可以随机选择
    this.colorChoices = [
      this.baseColor,
      this.complimentaryColor1,
      this.complimentaryColor2
    ];
  }

  randomColor() {
    // 选择随机颜色
    return this.colorChoices[~~random(0, this.colorChoices.length)].replace(
      "#",
      "0x"
    );
  }

  setCustomProperties() {
    // 设置 CSS 自定义属性，以便可以在整个 UI 中使用此处定义的颜色
    document.documentElement.style.setProperty("--hue", this.hue);
    document.documentElement.style.setProperty(
      "--hue-complimentary1",
      this.complimentaryHue1
    );
    document.documentElement.style.setProperty(
      "--hue-complimentary2",
      this.complimentaryHue2
    );
  }
}

// Orb class
class Orb {
  // Pixi 采用十六进制颜色作为十六进制文本（0x 而不是带有"#"的字符串）
  constructor(fill = 0x000000) {
    // 边界 = 球体"允许"在内移动的区域
    this.bounds = this.setBounds();
    // 将球体的 { x， y } 值初始化为其边界内的随机点
    this.x = random(this.bounds["x"].min, this.bounds["x"].max);
    this.y = random(this.bounds["y"].min, this.bounds["y"].max);

    // 球体相对于其原始半径的大小（这将随着时间的推移而调节）
    this.scale = 1;

    // 球体是什么颜色的？
    this.fill = fill;

    // 球体的原始半径，相对于窗口高度设置
    this.radius = random(window.innerHeight / 6, window.innerHeight / 3);

    // 噪声/自相似随机值的"时间"起点
    this.xOff = random(0, 1000);
    this.yOff = random(0, 1000);
    // 噪声/自身相似的随机值在时间上步进的速度
    this.inc = 0.002;

    // 图形用于将 2d 基元（在本例中为圆形）绘制到画布
    this.graphics = new PIXI.Graphics();
    this.graphics.alpha = 0.825;

    // 在最后一个窗口调整大小事件 250ms 后，重新计算球体位置。
    window.addEventListener(
      "resize",
      debounce(() => {
        this.bounds = this.setBounds();
      }, 250)
    );
  }

  setBounds() {
    // 每个球体可以移动到离 { x， y } 原点多远
    const maxDist =
      window.innerWidth < 1000 ? window.innerWidth / 3 : window.innerWidth / 5;
    // 每个球体的 { x， y } 原点（屏幕右下角）
    const originX = window.innerWidth / 1.25;
    const originY =
      window.innerWidth < 1000
        ? window.innerHeight
        : window.innerHeight / 1.375;

    // 允许每个球体移动x距离远离它的x / y原点
    return {
      x: {
        min: originX - maxDist,
        max: originX + maxDist
      },
      y: {
        min: originY - maxDist,
        max: originY + maxDist
      }
    };
  }

  update() {
    // 自相似的"伪随机"或"时间"中给定点的噪声值
    const xNoise = simplex.noise2D(this.xOff, this.xOff);
    const yNoise = simplex.noise2D(this.yOff, this.yOff);
    const scaleNoise = simplex.noise2D(this.xOff, this.yOff);

    // 将 xNoise/yNoise 值（介于 -1 和 1 之间）映射到球体边界内的某个点
    this.x = map(xNoise, -1, 1, this.bounds["x"].min, this.bounds["x"].max);
    this.y = map(yNoise, -1, 1, this.bounds["y"].min, this.bounds["y"].max);
    // 地图比例将噪声（介于 -1 和 1 之间）调整为比例值，介于球体原始大小的一半和原始大小的 100% 之间
    this.scale = map(scaleNoise, -1, 1, 0.5, 1);

    // step through "time"
    this.xOff += this.inc;
    this.yOff += this.inc;
  }

  render() {
    // 更新图形位置和比例值
    this.graphics.x = this.x;
    this.graphics.y = this.y;
    this.graphics.scale.set(this.scale);

    // 清除当前绘制到图形的任何内容
    this.graphics.clear();

    // 告诉图形用球体的填充颜色填充在此之后绘制的任何形状
    this.graphics.beginFill(this.fill);
    // 在 { 0， 0 } 处绘制一个圆，其大小由 this.radius 设置
    this.graphics.drawCircle(0, 0, this.radius);
    // 让图形知道我们不会再填充任何形状
    this.graphics.endFill();
  }
}

// Create PixiJS app
const app = new PIXI.Application({
  // 渲染为<canvas class="orb-canvas"></canvas>
  view: document.querySelector(".orb-canvas"),
  // 自动调整大小以适应当前窗口
  resizeTo: window,
  // 透明背景，稍后将使用CSS创建渐变背景
  transparent: true
});

// 创建调色板
const colorPalette = new ColorPalette();

app.stage.filters = [new KawaseBlurFilter(30, 10, true)];

// 创建球体
const orbs = [];

for (let i = 0; i < 10; i++) {
  const orb = new Orb(colorPalette.randomColor());

  app.stage.addChild(orb.graphics);

  orbs.push(orb);
}

// Animate!
if (!window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
  app.ticker.add(() => {
    orbs.forEach((orb) => {
      orb.update();
      orb.render();
    });
  });
} else {
  orbs.forEach((orb) => {
    orb.update();
    orb.render();
  });
}

document
  .querySelector(".overlay__btn--colors")
  .addEventListener("click", () => {
    colorPalette.setColors();
    colorPalette.setCustomProperties();

    orbs.forEach((orb) => {
      orb.fill = colorPalette.randomColor();
    });
  });
